from django.db import models

#dictionaries to store tabs and features for fast lookup.
_tabs_memo = {}
_features_memo = {}
_apps_memo = {}

def course_app(cls):
    """
    Decorator for classes to be added to the Course model.
    See the CoursesAPI wiki page for more info.
    
    Example:
    >>> from models import Course
    >>> @course_app
    >>> class MyApp(object):
    ...     slug = 'wiki'
    ...     mytab1 = App.Tab(slug='wiki', name='Wiki') #also set a view=#...
    ...     class MathTalkFeature(App.Feature):
    ...         slug = 'mathtalk'
    ...         def template_header(self, request=None):
    ...             return '<script type="text/javascript" src="js/latex/mathtalk.js">'
    ...
    >>> c.wiki.slug
    'wiki'
    >>> c.wiki.features
    [<Feature "mathtalk">]
    >>> c.wiki.tabs
    [<Tab "wiki">]
    >>> c.features.all()
    []
    >>> c.tabs.all()
    []
    >>> c.features.add('mathtalk')
    >>> c.save()
    >>> c.features.all()
    [<Feature "mathtalk">]
    >>> c.tabs.add('wiki')
    >>> c.save()
    >>> c.tabs.all()
    [<Tab "wiki">]
    """
    if not cls.slug or cls.slug is NotImplemented:
        raise NotImplementedError('Your app from "%s" doesn\'t have a slug!' 
            % cls.__module__
        )
        
    cls.tabs = []
    cls.features = []
    
    #register each tab and feature with self.tabs, self.features,
    #and the memos.
    for name, attr in cls.__dict__.iteritems():
        print 'Evaluating "%s: %s"...' % (name, attr.__repr__()),
        if isinstance(attr, App.Tab):
            print "it's a Tab!"
            print
            if(attr.slug in _tabs_memo):
                raise AttributeError(
                    'A Tab with slug "%s" got defined twice!' % attr.slug
                )
            _tabs_memo[attr.slug] = attr
            cls.tabs.append(attr)
        elif isinstance(attr, type) and issubclass(attr, App.Feature):
            print "it's a Feature!"
            if(feature.slug in _features_memo):
                raise AttributeError(
                    'A feature with slug "%s" got defined twice!' % attr.slug
                )
            _features_memo[attr.slug] = attr
            cls.features.append(attr)
        else:
            print
    #make the class inherit __init__ and __repr__ from App, if they haven't been
    #defined previously.
    for attr in ['__init__', '__repr__']:
        #has this class defined or inherited __init__ or __repr__?
        if getattr(cls,attr) is getattr(object,attr):
            cls.attr = App.attr
    
    def constructor(course):
        #check if we've already created an instance for this course.
        if (course.slug, cls.slug) in _apps_memo:
            return _apps_memo[(course.slug, cls.slug)]
        
        #no previous instance; create a new one.
        new_instance = cls(course)
        _apps_memo[(course.slug, cls.slug)] = new_instance
        return new_instance
    
    #hook into the Course class
    #this way calling c.myapp is the same as constructor(c)
    from models import Course
    setattr(Course, cls.slug, property(constructor))
    
    return cls
    
class App(object):
    """
    A base class for apps.
    """
    slug = None
    
    def __init__(self, course):
        self._course = course
    
    def __repr__(self):
        return '<%s for course "%s"' % (
            self.__name__, self._course.slug
        )
    
    class Tab(models.Model):
        DEFAULTS = {
            'slug' : property(lambda self: self.__module__),
        
            'title' : 'Untitled Tab',
        
            'view' : lambda self, request: None
        }
        
        def __str__(self):
            return self.slug
        
        def __unicode__(self):
            return str(self)
        
        def __repr__(self):
            return '<Tab "%s">' % self
        
        @property
        def relurl(self):
            return '%s/' % self.slug
                
        def __init__(self, **kwargs):
            for key in self.DEFAULTS:
                setattr(self, key, kwargs.get(key, False) or self.DEFAULTS[key])
                
    class Feature(object):
        slug = None
        
        def __str__(self):
            return self.slug
        
        def __unicode__(self):
            return str(self)
        
        def __repr__(self):
            return '<Feature "%s">' % self
        
        def template_header(self, request): 
            """
            Hook a header into the courses-generic template.  
            Should return a string, which is the header itself.
            Try using render_to_string if you want to use a template.
            """
            return ''
        
        def template_body(self, request):
            """
            Hook content within the <body> tag of the courses-generic template.
            Should return a string.
            Try using render_to_string.
            """
            return ''
        
        #general hook for adding context to templates 
        def template_hook(self, request, context, template):
            return None
        
        #hook all content.  Not sure how to implement this
        def content_hook(self, content):
            return content
        
        def template_context_processor(self, request):
            return {}
        
class TabField(models.CharField):
    description="A Tab object."
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 50 #max length is always 50
        super(TabField, self).__init__(*args, **kwargs)
    
    def to_python(self, value):
        return _tabs_memo[value]
    
    def get_prep_value(self, value):
        return value.slug

class FeatureField(models.CharField):
    description="A Feature class.  Contains methods for hooking into various things."
    
    # for some reason I can't subclass TabField because
    # it would send django into a fiery recursion loop
    # of doom.
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 50
        super(FeatureField, self).__init__(*args, **kwargs)
        
    get_prep_value = lambda self, value: value.slug
    
    def to_python(self, value):
        return _features_memo[value]
